package com.badrit.core.clusteringAlgorithms
{
	import com.vizsage.as3mathlib.math.alg.Matrix;
	
	import flash.filesystem.File;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;
	
	public class BaseClusteringAlgorithm
	{
		public static const Epsolon:Number = 1;
		public static const MAX_ITERATIONS:int = 25;
		public static const N_TRIALS:int = 30;
		
		public static const DHB:int = 0;		
		public static const DHF:int = 1;
		public static const ABF:int = 2;
		public static const AFB:int = 3;
		public static const K_MEANS:int = 4;
		/**
		 * NxK
		 * 2-dimensional array holds the membership, 
		 * each row represents membershipt of pattern in on of k classes : n rows
		 * each column represent a class : k columns
		 **/
		protected var W:Matrix;
		
		public var M:Array;
		
		/**
		 * NxM
		 * 2-dimensional array holds the patterns, 
		 * each row hold a pattern: n patterns
		 * each column represent a dimension: m dimensions
		**/
		protected var patterns:Matrix;
		
		protected var centers:Matrix;				
		
		protected var nPatterns:Number = 0;
		protected var kCenters:Number = 0;
		protected var mDimensions:Number = 0;
		protected var kClasses:Number = 0;				
		
		public static function readInputFile(file:File):Matrix{
			var fileStream:FileStream = new FileStream();
						
			fileStream.open(file, FileMode.READ);
			
			
			var nDimensions:int = 0;						
			var inputString:String = fileStream.readUTFBytes(fileStream.bytesAvailable);
			var lines:Array = inputString.split(/\n\r|\n|\r\n/);
			if(lines.length > 0)		
				nDimensions = lines[0].split(/,|\s+/).length;
													
			var patterns:Matrix =  new Matrix(lines.length, nDimensions);
			
			
			for (var n:int; n < lines.length; n++){					
				var entries:Array = lines[n].split(/,|\s+/, nDimensions);
				for(var m:int=0; m < nDimensions; m++) {
					(patterns.$m[n] as Array)[m] = entries[m];
				}
			}		
			
			return patterns;		
		}
		public function BaseClusteringAlgorithm(patterns:Matrix, k:Number)
		{
			init(patterns, k);	
		}
		
		/**
		 * returns the cluster index of the given pattern index
		 **/
		public function getClusterForPattern(patternIndex:int):int {
			var patternMemberShip:Array = W.$m[patternIndex] as Array;
			for( var j:int = 0; j < kClasses ; j++){
				if(patternMemberShip[j] == 1)
					return j;
			}
			
			return -1;
		}
		
		protected function calculateSSE():Number{
			var objectiveFunction:Number = 0;
			for( var i:int = 0; i < nPatterns; i++){					
				var k:int = getClusterForPattern(i)
				var distance:Number;
				distance = calcSquaredEucledianDistance(patterns.$m[i], centers.$m[k]);
				objectiveFunction += distance * distance;																			
			}
			
			return objectiveFunction / (nPatterns + 1);
		}
		
		protected function calculateObjectiveFunction():Number{
			var objectiveFunction:Number = 0;
			for( var i:int = 0; i < nPatterns; i++){					
				var k:int = getClusterForPattern(i)
				var distance:Number;
				distance = calcEucledianDistance(patterns.$m[i], centers.$m[k]);
				objectiveFunction += distance * distance;																			
			}
			
			return objectiveFunction ;
		}
		public function applyAlgorithmNTimes(nTrials:Number=N_TRIALS):Array{
								
			var objectiveFunctionValues:Array =	
									new Array();
					
			for( var i:int; i < nTrials; i++){	
				initializeMembership();
				initializeCentersToZeros();
								
				objectiveFunctionValues.push(applyAlgorithm());
			}
			
			return objectiveFunctionValues;
		}
		
		private function initializeCentersToZeros():void {
			for( var k:int = 0; k < kClasses; k++){
				setValueToAllElements(centers.$m[k], 0);
			}
		}
		
		private function setValueToAllElements(array:Array, value:Object):void {
			for(var i:int = 0; i<array.length; i++){
				array[i] = value;
			}
		}
		
		/**
		 * initialize membershipt array at random 
		 * @return 		 
		 */		
		private function initializeMembership():void {
			W = new Matrix(nPatterns, kClasses);
			M = new Array(nPatterns);
			
			for( var i:int = 0; i < nPatterns; i++){							
				var classNumber:int = Math.random() * kClasses;
				classNumber = ( classNumber > kClasses )? kClasses : classNumber;
				
				assignPatternToCluster(i, classNumber);	
				M[i] = classNumber;						
			}
		}
		
		/**
		 * Assign the given pattern to the specified cluster by setting the corresponding
		 * entry in W matrix to 1 and others to 0
		 */
		protected function assignPatternToCluster(patternIndex:int, cluster:int):void{
		 	setValueToAllElements(W.$m[patternIndex], 0);
		 	
			(W.$m[patternIndex] as Array)[cluster] = 1;						
		}
				
		/**
		 * Calculate centers based on current cluster distribution
		 **/ 
		protected function calculateCenters():void {												
			for( var k:int = 0; k < kClasses; k++){
				calculateCenterOfClass(k);
			}
		}		
		
		protected function calculateCenterOfClass(k:int):void{
			var center:Array ;
			var cluster:Array = getClusterPatterns(k);
				
			center = centers.$m[k];
			setValueToAllElements(center, 0);
		
			if(cluster.length == 0)
				return;
			
			// calculate center of class k
			for( var l:int = 0; l < cluster.length; l++){
				var pattern:Array = cluster[l] as Array;
				
				for( var j:int = 0; j < mDimensions; j++){
					center[j] += pattern[j] / cluster.length;
				}
			}
		}	
		 
		 protected function getClusterPatterns(k:int):Array{
		 	var cluster:Array = new Array();
								
			// fill cluster array with patterns of class k
			for( var i:int = 0; i < nPatterns; i++){
				if( getClusterForPattern(i) == k ){
					cluster.push(patterns.$m[i]); 
				}		
			}										
			
			return cluster;
		 }
		 private function init(patterns:Matrix, k:Number):void {
			this.patterns = patterns;
			
			nPatterns = patterns.$rows;
			mDimensions = patterns.$columns;
			kClasses = k;
			kCenters = k;
			
			centers = new Matrix(k, mDimensions);					
		}	
		
		
		protected function calcSquaredEucledianDistance(p1:Array, p2:Array):Number {	    	      	
			var sumSquared:Number = 0;
			
			for( var i:int = 0; i < p1.length; i++){
				var delta:int = (p1[i] - p2[i]);
				sumSquared += delta * delta;
			}
	        
	        return sumSquared;
	    }	
		/**	    
	     * calculate the distance between two points.	     	     
	     **/
	    protected function calcEucledianDistance(p1:Array, p2:Array):Number {	    	      	
			var sumSquared:Number = 0;
			
			for( var i:int = 0; i < p1.length; i++){
				var delta:int = (p1[i] - p2[i]);
				sumSquared += delta * delta;
			}
	        
	        return Math.sqrt(sumSquared);
	    }		
	    
		protected function getClosestCenterToPattern(i:int):int{
			var minDistance:Number = Number.MAX_VALUE;
			var minDistanceCenter:int = 0;
			
			for( var k:int = 0; k < kClasses; k++){
				var distance:Number;
				distance = calcEucledianDistance(patterns.$m[i], centers.$m[k]);
				
				if(distance < minDistance){
					minDistance = distance;
					minDistanceCenter = k;
				}	
			}
			
			return minDistanceCenter;
		}
		
		public function applyAlgorithm():Number{throw new Error("Not implemented method");};
	}
}